Como utilizar una matriz de leds.
En este caso usaremos el componente 1088AS que es una matriz de leds de 8x8 (64 leds).
El esquema de conexiones de este modulo es el siguiente:
NOTA: para identificar el pin 1, miramos el modulo con los leds hacia arriba y las letras del lateral (1088AS) hacia abajo, el pin 1 es el que está abajo a la izquierda.
En una matriz de leds todos los leds están en paralelo, esto genera que al querer encender mas de un led en filas y columnas distintas se enciendan otros leds que no queremos.
Si por ejemplo queremos encender dos leds simultaneamente que están en una fila y columna distintas, por ejemplo el led (1,2) y el led (3,3), lo que ocurrirá es que se encenderán dos leds a mayores el (1,3) y el (3,2).
Para evitar este comportamiento lo que se hace es aprovecharse de una caracteristica del ojo humano que es el POV, la retina mantiene por un periodo de tiempo la luz que recibe (unos 15 milisegundos), y esto nos permite crear el efecto de que hay un led encendido aún cuando en realidad lo estamos encendiendo y apagando a mucha velocidad.
Lo que se hace normalmente para aprovechar este efecto y evitar que se enciendan leds que no queremos es ir encendiendo los leds por filas, de manera que vamos avanzando cada X microsegundos a la siguiente fila de una en una y encendemos todos los leds que queremos para generar la imagen deseada, esto al final para el ojo humano resulta en una imagen fija, a pesar de que en realidad los leds estén parpadeando a una velocidad muy alta, de hecho normalmente este efecto es captado por las camaras, dependiendo de la velocidad de obturación que tengan configurada, por eso en ocasiones en algunos dispositivos se ven las indicaciones led que tengan que parpadean de manera extraña al grabarlos en video.
El circuito consisten en conectar los puertos del registro B (los 8 puertos) al anodo de los leds de la matriz mediante una resistencia y los puertos del registro D (los 8 puertos) al catodo de los leds de la matriz. (El registro C solo tiene 7 puertos, por eso no lo usamos en este caso para simplificar el circuito)
El circuito es simple, pero tiene muchos cables por lo que se puede hacer un poco lioso.
El código más simple para hacer uso de la matriz de leds con el efecto POV es el siguiente:
NOTA: en el array de caracteres se pueden intuir los caracteres dibujados con unos, pero hay que tener en cuenta que están invertidos horizontalmente, ya que el bit menos significativo (el que está a la derecha), hace referencia al led que está mas a la izquierda en la matriz de leds.
#include <util/delay.h>
#include <avr/io.h>
volatile uint8_t characters[][8] =
{
{
0b00011000,
0b00111100,
0b01100110,
0b01000010,
0b01111110,
0b01000010,
0b01000010,
0b00000000
},
{
0b00111110,
0b01000010,
0b01000010,
0b00111110,
0b01000010,
0b01000010,
0b00111110,
0b00000000
}
};
int main(void) {
DDRB = 0xFF; //Todos los puertos como salidas
DDRD = 0xFF;
uint8_t count = 0;
while (1) {
PORTB = 0x00; //Apagamos todos los leds para evitar efecto de iluminacion fantasma en algunos leds
PORTD = 0x00;
PORTD = ~(1 << count);
PORTB = characters[1][count];
_delay_ms(1);
count++;
if(count >= 8)
count=0;
}
}
El código va encendiendo los leds fila por fila para generar el efecto POV y al hacerlo fila por fila evita que se enciendan leds que no queremos que se enciendan
Un detalle a tener en cuenta es que al principio de cada iteracción del bucle se apagan todos los leds, si no hacemos esto se genera un pequeño efecto ghosting en los leds, ya que al pasar a la siguiente linea se mantendrían encendidas las columnas de la fila previa en la nueva fila, esto ocurre solo por un ciclo de reloj (aproximadamente 1 microsegundo), pero es suficiente para generar ese efecto de leds minimamente encendidos.
Una manera mas óptima de manejar la matriz de leds es usando uno de los timers del microcontrolador para generar el efecto POV y después seleccionar en el bucle principal que queremos mostrar:
#include <util/delay.h>
#include <avr/io.h>
#include <avr/interrupt.h>
volatile uint8_t characters[][8] =
{
{
0b00011000,
0b00111100,
0b01100110,
0b01000010,
0b01111110,
0b01000010,
0b01000010,
0b00000000
},
{
0b00111110,
0b01000010,
0b01000010,
0b00111110,
0b01000010,
0b01000010,
0b00111110,
0b00000000
}
};
volatile uint8_t characterSelected = 0;
volatile uint8_t count = 0;
ISR(TIMER0_COMPA_vect) {
PORTB = 0x00; //Apagamos todos los leds para evitar efecto de iluminacion fantasma en algunos leds
PORTD = 0x00;
PORTD = ~(1 << count);
PORTB = characters[characterSelected][count];
count++;
if(count >= 8)
count=0;
}
void init_timer0(){
TCCR0A |= (1 << WGM01);
TCCR0B |= (1 << CS02); // Prescaler de 256
TIMSK0 |= (1 << OCIE0A);
OCR0A = 2; // Interrupción cada 512 microsegundos
sei();
}
int main(void) {
DDRB = 0xFF; //Todos los puertos como salidas
DDRD = 0xFF;
init_timer0();
while (1) {
characterSelected = 1;
_delay_ms(1000);
characterSelected = 0;
_delay_ms(1000);
}
}
NOTA: Aunque pongamos un delay de 1000ms en el bucle principal el delay será algo mayor de esos 1000ms, porque en este escenario estamos lanzando muchas interrupciones (1 cada 512 microsegundos), por lo tanto el delay será interrunpido muchas veces haciendo que tarde más de lo que indicamos.
En el ejemplo se muestran 4 caracteres (espacio, A, B y C), para usar todos los caracteres se puede usar el siguiente archivo de cabecera characters.h
#include <util/delay.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#define DISPLAY_COLUMNS 8
#define DISPLAY_ROWS 8
#define SLIDE_SPEED_DELAY 60
#define CHAR_LIST_SIZE 4
#define SPACE 0
#define A 1
#define B 2
#define C 3
volatile uint8_t characters[][8] =
{
//Space
{
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
},
// A
{
0b00011000,
0b00111100,
0b01100110,
0b01000010,
0b01111110,
0b01000010,
0b01000010,
0b00000000,
},
// B
{
0b00111110,
0b01000010,
0b01000010,
0b00111110,
0b01000010,
0b01000010,
0b00111110,
0b00000000,
},
// C
{
0b00111100,
0b01000010,
0b00000010,
0b00000010,
0b00000010,
0b01000010,
0b00111100,
0b00000000,
}
};
volatile uint8_t display[8];
volatile uint8_t count = 0;
ISR(TIMER0_COMPA_vect) {
PORTB = 0x00; //Apagamos todos los leds para evitar efecto de iluminacion fantasma en algunos leds
PORTD = 0x00;
PORTD = ~(1 << count);
PORTB = display[count];
count++;
if(count >= 8)
count=0;
}
void init_timer0(){
TCCR0A |= (1 << WGM01);
TCCR0B |= (1 << CS02); // Prescaler de 256
TIMSK0 |= (1 << OCIE0A);
OCR0A = 2; // Interrupción cada 512 microsegundos
sei();
}
void print_slide_characters(uint8_t *char_list){
uint8_t current_column = 0;
uint8_t current_char = 0;
while(current_char < CHAR_LIST_SIZE){
for(uint8_t i = 0; i < DISPLAY_ROWS; i++){
display[i] = (display[i] >> 1); // slide pixels
uint8_t character_id = char_list[current_char];
display[i] |= ((characters[character_id][i] >> current_column) & 0b00000001) << 7; // Feed last column with new char (<<7 to put LSB as MSB)
}
current_column++;
if(current_column == DISPLAY_COLUMNS - 1){
current_column = 0;
current_char++;
}
_delay_ms(SLIDE_SPEED_DELAY);
}
}
int main(void) {
DDRB = 0xFF; //Todos los puertos como salidas
DDRD = 0xFF;
init_timer0();
uint8_t char_list[CHAR_LIST_SIZE] = {SPACE, A, B, C};
while (1) {
print_slide_characters(char_list);
}
}
AVR | led | matrix | pov